#include "stdafx.h"

#include "keydef.h"
#include "keybdef.h"
#include "keybicon.h"

#include "setupdialog.h"

#include <map>

/////////////////////////////////////////////
//                                         //
// Minimizing C++ Win32 App To System Tray //
//                                         //
// You found this at bobobobo's weblog,    //
// http://bobobobo.wordpress.com           //
//                                         //
// Creation date:  Mar 30/09               //
// Last modified:  Mar 30/09               //
//                                         //
/////////////////////////////////////////////

// GIVING CREDIT WHERE CREDIT IS DUE!!
// Thanks ubergeek!  http://www.gidforums.com/t-5815.html

#pragma region include and define

#ifdef UNICODE
#define stringcopy wcscpy
#else
#define stringcopy strcpy
#endif

#define ID_TRAY_APP_ICON                5000
#define ID_TRAY_EXIT_CONTEXT_MENU_ITEM  3000
#define ID_TRAY_CONFIG_CONTEXT_MENU_ITEM  3001
#define ID_TRAY_UPDATE_CONTEXT_MENU_ITEM  3002
#define WM_TRAYICON ( WM_USER + 1 )
#pragma endregion

#pragma region constants and globals
UINT WM_TASKBARCREATED = 0 ;

// put g_hwnd into shared memory segment
#pragma comment(linker, "/SECTION:.shr,RWS")
#pragma data_seg(".shr")
HWND g_hwnd = NULL;
#pragma data_seg()

// local stuff
SetupDialog *m_setupDialog;

// configurator path
wchar_t szConfPath[MAX_PATH] = {0};
wchar_t szUpdPath[MAX_PATH] = {0};

// handles
HMENU g_menu;
HHOOK g_lhook;
NOTIFYICONDATA g_notifyIconData;

// layouts
InputLayouts layouts;

#pragma endregion


LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM);



// keyboard stuff
extern void setKeyHook();
extern void unsetKeyHook();

extern std::vector<KB_Struct> switchCases;

// icons
KB_Icon kb_icons;



#pragma region helper funcs
// These next 2 functions are the STARS of this example.
// They perform the "minimization" function and "restore"
// functions for our window.  Notice how when you "minimize"
// the app, it doesn't really "minimize" at all.  Instead,
// you simply HIDE the window, so it doesn't display, and
// at the same time, stick in a little icon in the system tray,
// so the user can still access the application.
void Minimize()
{
    // add the icon to the system tray
    Shell_NotifyIcon(NIM_ADD, &g_notifyIconData);

    // ..and hide the main window
    ShowWindow(g_hwnd, SW_HIDE);
}

// Basically bring back the window (SHOW IT again)
// and remove the little icon in the system tray.
void Restore()
{
    // Remove the icon from the system tray
    //Shell_NotifyIcon(NIM_DELETE, &g_notifyIconData);

    // ..and show the window
    //ShowWindow(g_hwnd, SW_SHOW);
}

// Initialize the NOTIFYICONDATA structure.
// See MSDN docs http://msdn.microsoft.com/en-us/library/bb773352(VS.85).aspx
// for details on the NOTIFYICONDATA structure.
void InitNotifyIconData()
{
    memset( &g_notifyIconData, 0, sizeof( NOTIFYICONDATA ) ) ;

    g_notifyIconData.cbSize = sizeof(NOTIFYICONDATA);

    /////
    // Tie the NOTIFYICONDATA struct to our
    // global HWND (that will have been initialized
    // before calling this function)
    g_notifyIconData.hWnd = g_hwnd;
    // Now GIVE the NOTIFYICON.. the thing that
    // will sit in the system tray, an ID.
    g_notifyIconData.uID = ID_TRAY_APP_ICON;
    // The COMBINATION of HWND and uID form
    // a UNIQUE identifier for EACH ITEM in the
    // system tray.  Windows knows which application
    // each icon in the system tray belongs to
    // by the HWND parameter.
    /////

    /////
    // Set up flags.
    g_notifyIconData.uFlags = NIF_ICON | // promise that the hIcon member WILL BE A VALID ICON!!
        NIF_MESSAGE | // when someone clicks on the system tray icon,
        // we want a WM_ type message to be sent to our WNDPROC
        NIF_TIP;      // we're gonna provide a tooltip as well, son.

    g_notifyIconData.uCallbackMessage = WM_TRAYICON; //this message must be handled in hwnd's window procedure. more info below.

    // Load da icon.  Be sure to include an icon "green_man.ico" .. get one
    // from the internet if you don't have an icon
    g_notifyIconData.hIcon = (HICON)LoadImage( NULL, TEXT("ru.ico"), IMAGE_ICON, 0, 0, LR_LOADFROMFILE  ) ;

    // set the tooltip text.  must be LESS THAN 64 chars
    stringcopy(g_notifyIconData.szTip, TEXT("KeySwitcher is active"));
}
#pragma endregion


//extern void checkKeyStates();

VOID CALLBACK LocaleHook(      
    HWND hwnd,
    UINT uMsg,
    UINT_PTR idEvent,
    DWORD dwTime
)
{
    static HKL last_id = 0;
    HWND hWnd=GetForegroundWindow();
    DWORD act_thread = GetWindowThreadProcessId(hWnd, NULL);
    HKL id = GetKeyboardLayout(act_thread);
    if (id != last_id)
    {
        last_id = id;

		HICON icon = kb_icons.HKL2HICON(id);
        g_notifyIconData.hIcon = icon;

		stringcopy(g_notifyIconData.szTip, TEXT("Unknown locale"));
		for (int i = 0; i < layouts.size(); i++)
			if (layouts.at(i).hkl == id)
			{
				stringcopy(g_notifyIconData.szTip, layouts.at(i).name.c_str());
				break;
			}

        Shell_NotifyIcon(NIM_MODIFY, &g_notifyIconData);
    }

	// TEMP
	//checkKeyStates();
}

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR args, int iCmdShow )
{
    //For single instance checking
    if (g_hwnd)
    {
        // terminates the creation
        return 1;
    }

	// configurator & updater executables

	// get module path
	char szAppPath[MAX_PATH]      = "";
	char szAppDirectory[MAX_PATH] = "";

	::GetModuleFileNameA(0, szAppPath, sizeof(szAppPath) - 1);

	// Extract directory
	strncpy(szAppDirectory, szAppPath, strrchr(szAppPath, '\\') - szAppPath);
	szAppDirectory[strlen(szAppDirectory)] = '\0';

	// add config name
	strcpy(szAppPath+strlen(szAppDirectory), "\\keyconfig.exe");
	MultiByteToWideChar(CP_ACP, MB_COMPOSITE, szAppPath, strlen(szAppPath), szConfPath, strlen(szAppPath)*2);

	strcpy(szAppPath+strlen(szAppDirectory), "\\updater.exe");
	MultiByteToWideChar(CP_ACP, MB_COMPOSITE, szAppPath, strlen(szAppPath), szUpdPath, strlen(szAppPath)*2);



    TCHAR className[] = TEXT( "tray icon class" );

    // I want to be notified when windows explorer
    // crashes and re-launches the taskbar.  the WM_TASKBARCREATED
    // event will be sent to my WndProc() AUTOMATICALLY whenever
    // explorer.exe starts up and fires up the taskbar again.
    // So its great, because now, even if explorer crashes,
    // I have a way to re-add my system tray icon in case
    // the app is already in the "minimized" (hidden) state.
    // if we did not do this an explorer crashed, the application
    // would remain inaccessible!!
    WM_TASKBARCREATED = RegisterWindowMessageA("TaskbarCreated") ;

#pragma region add a console
    // add a console, because I love consoles.
    // To disconnect the console, just comment out
    // the next 3 lines of code.
    //// AllocConsole();
    //// AttachConsole( GetCurrentProcessId() ) ;
    //// freopen( "CON", "w", stdout ) ;
#pragma endregion

#pragma region get window up
    WNDCLASSEX wnd = { 0 };

    wnd.hInstance = hInstance;
    wnd.lpszClassName = className;
    wnd.lpfnWndProc = WndProc;
    wnd.style = CS_HREDRAW | CS_VREDRAW ;
    wnd.cbSize = sizeof (WNDCLASSEX);

    wnd.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wnd.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wnd.hCursor = LoadCursor (NULL, IDC_ARROW);
    wnd.hbrBackground = (HBRUSH)COLOR_APPWORKSPACE ;

    if (!RegisterClassEx(&wnd))
    {
        FatalAppExit( 0, TEXT("Couldn't register window class!") );
    }

    // setup keyboard hook
    setKeyHook();

    // setup locale hook
    SetTimer(NULL,0,200,LocaleHook);

	// load layouts
	initLayoutList(layouts);

	// load icons
	kb_icons.loadIcons(layouts);

    // create dialog
    m_setupDialog = new SetupDialog(hInstance, className);
    g_hwnd = m_setupDialog->hwnd;

    // Initialize the NOTIFYICONDATA structure once
    InitNotifyIconData();

    //ShowWindow (g_hwnd, iCmdShow);
    Minimize();
#pragma endregion

    MSG msg;
    while (GetMessage (&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    // Once you get the quit message, before exiting the app,
    // clean up and remove the tray icon
    //if( !IsWindowVisible( g_hwnd ) )
    //{
        Shell_NotifyIcon(NIM_DELETE, &g_notifyIconData);
    //}

    // unset key hook
    unsetKeyHook();

    // kill dialog
    delete m_setupDialog;

    return msg.wParam;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    if ( message==WM_TASKBARCREATED && !IsWindowVisible( g_hwnd ) )
    {
        Minimize();
        return 0;
    }

    switch (message)
    {
    case WM_CREATE:

        // create the menu once.
        // oddly, you don't seem to have to explicitly attach
        // the menu to the HWND at all.  This seems so ODD.
        g_menu = CreatePopupMenu();

		AppendMenu(g_menu, MF_STRING, ID_TRAY_CONFIG_CONTEXT_MENU_ITEM,  TEXT( "Configure..." ) );
		AppendMenu(g_menu, MF_STRING, ID_TRAY_UPDATE_CONTEXT_MENU_ITEM,  TEXT( "Check for updates..." ) );
		AppendMenu(g_menu, MF_SEPARATOR, NULL, NULL);
        AppendMenu(g_menu, MF_STRING, ID_TRAY_EXIT_CONTEXT_MENU_ITEM,  TEXT( "Exit" ) );

        break;

    case WM_SYSCOMMAND:
        switch( wParam & 0xfff0 )  // (filter out reserved lower 4 bits:  see msdn remarks http://msdn.microsoft.com/en-us/library/ms646360(VS.85).aspx)
        {
        case SC_MINIMIZE:
        case SC_CLOSE:  // redundant to WM_CLOSE, it appears
            Minimize() ;
            return 0 ;
            break;
        }
        break;

        // Our user defined WM_TRAYICON message.
        // We made this message up, and we told
        //
    case WM_TRAYICON:
        {
            //printf( "Tray icon notification, from %d\n", wParam ) ;

            switch(wParam)
            {
            case ID_TRAY_APP_ICON:
                //printf( "Its the ID_TRAY_APP_ICON.. one app can have several tray icons, ya know..\n" ) ;
                break;
            }

            // the mouse button has been released.

            // I'd LIKE TO do this on WM_LBUTTONDOWN, it makes
            // for a more responsive-feeling app but actually
            // the guy who made the original post is right.
            // Most apps DO respond to WM_LBUTTONUP, so if you
            // restore your window on WM_LBUTTONDOWN, then some
            // other icon will scroll in under your mouse so when
            // the user releases the mouse, THAT OTHER ICON will
            // get the WM_LBUTTONUP command and that's quite annoying.
            if (lParam == WM_LBUTTONUP)
            {
                //printf( "You have restored me!\n" ) ;
                //Restore();

				// run configurator
				ShellExecute(NULL, _T("open"), szConfPath, NULL, NULL, SW_SHOW);
            }
            else if (lParam == WM_RBUTTONDOWN) // I'm using WM_RBUTTONDOWN here because
            {
                //printf( "Mmm.  Let's get contextual.  I'm showing you my context menu.\n" ) ;
                // it gives the app a more responsive feel.  Some apps
                // DO use this trick as well.  Right clicks won't make
                // the icon disappear, so you don't get any annoying behavior
                // with this (try it out!)

                // Get current mouse position.
                POINT curPoint ;
                GetCursorPos( &curPoint ) ;

                // should SetForegroundWindow according
                // to original poster so the popup shows on top
                SetForegroundWindow(hwnd); 

                // TrackPopupMenu blocks the app until TrackPopupMenu returns
                //printf("calling track\n");
                UINT clicked = TrackPopupMenu(

                    g_menu,
                    TPM_RETURNCMD | TPM_NONOTIFY, // don't send me WM_COMMAND messages about this window, instead return the identifier of the clicked menu item
                    curPoint.x,
                    curPoint.y,
                    0,
                    hwnd,
                    NULL

                    );
                //printf("returned from call to track\n");

                // Original poster's line of code.  Haven't deleted it,
                // but haven't seen a need for it.
                //SendMessage(hwnd, WM_NULL, 0, 0); // send benign message to window to make sure the menu goes away.
                if (clicked == ID_TRAY_EXIT_CONTEXT_MENU_ITEM)
                {
                    // quit the application.
                    //printf("I have posted the quit message, biatch\n");
                    PostQuitMessage( 0 ) ;
                }
				else
                if (clicked == ID_TRAY_CONFIG_CONTEXT_MENU_ITEM)
                {
					// run configurator
					ShellExecute(NULL, _T("open"), szConfPath, NULL, NULL, SW_SHOW);
                }
				else
                if (clicked == ID_TRAY_UPDATE_CONTEXT_MENU_ITEM)
                {
					// run updater
					ShellExecute(NULL, _T("open"), szUpdPath, NULL, NULL, SW_SHOW);
                }

            }
        }
        break;

        // intercept the hittest message.. making full body of
        // window draggable.
    case WM_NCHITTEST:
        {
            // http://www.catch22.net/tuts/tips
            // this tests if you're on the non client area hit test
            UINT uHitTest = DefWindowProc(hwnd, WM_NCHITTEST, wParam, lParam);
            if(uHitTest == HTCLIENT)
                return HTCAPTION;
            else
                return uHitTest;
        }

    case WM_CLOSE:
        //printf( "Got an actual WM_CLOSE Message!  Woo hoo!\n" ) ;
        Minimize() ;
        return 0;
        break;

    case WM_DESTROY:
        //printf( "DESTROY!!\n" ) ;
	    PostQuitMessage (0);
        break;

    }

    return DefWindowProc( hwnd, message, wParam, lParam ) ;
}
